#include <DiskStream.hpp>

Modeling::DiskStream::~DiskStream()
{
	fclose(this->file);
}

Modeling::DiskStream::DiskStream(std::string name, std::string mode)
{
	this->file = fopen(name.c_str(), mode.c_str());
	if (this->file == NULL) {
		throw Exceptions::FileNotFoundException(name + std::string(" No such file or directory."));
	}
}

size_t Modeling::DiskStream::read(Modeling::Scene* scene)
{
	Modeling::Animation* animation = scene->createAnimation();
	this->read(animation);
	Modeling::Root* root = scene->createRoot();
	this->read(root);

	return 0;
}

size_t  Modeling::DiskStream::read(Modeling::Animation* animation)
{
	size_t bytes = 0;

	unsigned int frames;
	bytes += this->read(&frames);
	animation->setFrames(frames);
	unsigned int FPS;
	bytes += this->read(&FPS);
	animation->setFPS(FPS);

	return bytes;
}

size_t Modeling::DiskStream::read(Modeling::Root* root)
{
	std::string name;
	this->read(&name);
	root->setName(name);

	this->root = root;
	this->readMaterials(root);

	unsigned int nodesQty;
	this->read(&nodesQty);
	for (unsigned int i=0 ; i<nodesQty ; i++) {
		std::string nodeName;
		std::string nodeParent;
		int nodeType;

		this->read(&nodeName);
		this->read(&nodeParent);
		this->read(&nodeType);
		switch (nodeType)
		{
			case Modeling::MESH:
			{
				Modeling::Mesh* mesh = root->addMesh(nodeParent, nodeName);
				unsigned int matQty;
				this->read(&matQty);
				for (unsigned int j=0 ; j<matQty ; j++) {
					Mathing::Matrix4f mat;
					this->read(&mat);
					mesh->addMatrix(mat);
				}
				this->read(mesh);
			}
			break;

			case Modeling::CAMERA:
			{
				Modeling::Camera* camera = root->addCamera(nodeParent, nodeName);
				this->read(camera);
			}
			break;

			case Modeling::LIGHT:
			{
				Modeling::Light* light = root->addLight(nodeParent, nodeName);
				this->read(light);
			}
			break;
		}
	}

	return 0;
}

size_t Modeling::DiskStream::read(Modeling::Mesh* mesh)
{
	unsigned int vertexQty;
	this->read(&vertexQty);
	for (unsigned int i=0 ; i<vertexQty ; i++) {
		Vector3f vertex;
		this->read(&vertex);
		mesh->addVertex(vertex);
	}

	unsigned int mapCoordsQty;
	this->read(&mapCoordsQty);
	for (unsigned int i=0 ; i<mapCoordsQty ; i++) {
		Vector2f coord;
		this->read(&coord);
		mesh->addMapCoord(coord);
	}

	unsigned int polysQty;
	this->read(&polysQty);
	for (unsigned int i=0 ; i<polysQty ; i++) {
		Modeling::Polygon* polygon = mesh->addPolygon();

		Vector3ui index;
		this->read(&index);
		for (unsigned int j=0 ; j<3 ; j++) {
			polygon->addVertex(mesh->getVertex(index[j]));
		}

		for (unsigned int j=0 ; j<3 ; j++) {
			Vector3f normal;
			this->read(&normal);
			polygon->addNormal(normal);
		}

		std::string matName;
		this->read(&matName);
		polygon->setMaterial(this->root->getMaterial(matName));
	}

	return 0;
}

size_t Modeling::DiskStream::read(Modeling::Camera* camera)
{
	size_t bytes = 0;
	return bytes;
}

size_t Modeling::DiskStream::read(Modeling::Light* light)
{
	size_t bytes = 0;
	return bytes;
}

size_t Modeling::DiskStream::readMaterials(Modeling::Root* root)
{
	size_t bytes = 0;
	unsigned int matQty;
	bytes += this->read(&matQty);
	
	for (unsigned int i=0 ; i<matQty ; i++) {
		std::string name;
		bytes += this->read(&name);
		Modeling::Material* material = root->addMaterial(name);
		bytes += this->read(material);
	}
	return bytes;
}

size_t Modeling::DiskStream::read(Modeling::Material* material)
{
	size_t bytes = 0;

	Modeling::RGBf ambient;
	bytes += this->read(&ambient);
	material->setAmbient(ambient);

	Modeling::RGBf diffuse;
	bytes += this->read(&diffuse);
	material->setDiffuse(diffuse);

	Modeling::RGBf specular;
	bytes += this->read(&specular);
	material->setSpecular(specular);

	float shininess;
	bytes += this->read(&shininess);
	material->setShininess(shininess);

	Modeling::Map* diffuseMap = material->enableDiffuseMap();
	bytes += this->read(diffuseMap);

	return bytes;
}

size_t Modeling::DiskStream::read(Modeling::Map* map)
{
	size_t bytes = 0;

	std::string filename;
	bytes += this->read(&filename);
	map->setFileName(filename);

	return bytes;
}

size_t Modeling::DiskStream::write(Modeling::Scene* scene)
{
	Modeling::Animation* animation = scene->getAnimation();
	this->write(animation);
	Modeling::Root* root = scene->getRoot();
	this->write(root);

	return 0;
}

size_t  Modeling::DiskStream::write(Modeling::Animation* animation)
{
	size_t bytes = 0;

	unsigned int frames = animation->getFrames();
	bytes += this->write(frames);
	unsigned int FPS = animation->getFPS();
	bytes += this->write(FPS);

	return bytes;
}

size_t Modeling::DiskStream::write(Modeling::Root* root)
{
	size_t bytes = 0;
	bytes += this->write(root->getName());
	bytes += this->writeMaterials(root);
	bytes += this->write(root->getNodesQty());
	bytes += this->writeChildren(root);
	return bytes;
}

size_t Modeling::DiskStream::writeChildren(Modeling::Node* node)
{
	size_t bytes = 0;

	for (unsigned int i=0 ; i<node->getChildrenQty() ; i++) {
		Modeling::Node* child = node->getChild(i);

		bytes += this->write(child->getName());
		bytes += this->write(node->getName());
		bytes += this->write(child->type());

		unsigned int matQty = child->getMatricesQty();
		bytes += this->write(matQty);
		for (unsigned int j=0 ; j<matQty ; j++) {
			bytes += this->write(*child->getMatrix(j));
		}

		bytes += this->castNode(child);
		bytes += this->writeChildren(child);
	}

	return bytes;
}

size_t Modeling::DiskStream::castNode(Modeling::Node* node)
{
	size_t bytes = 0;

	switch (node->type())
	{
		case Modeling::MESH:
		{
			Modeling::Mesh* mesh = (Modeling::Mesh*)node;
			bytes += this->write(mesh);
		}
		break;

		case Modeling::CAMERA:
		{
			Modeling::Camera* camera = (Modeling::Camera*)node;
			bytes += this->write(camera);
		}
		break;

		case Modeling::LIGHT:
		{
			Modeling::Light* light = (Modeling::Light*)node;
			bytes += this->write(light);
		}
		break;
	}
	return bytes;
}

size_t Modeling::DiskStream::write(Modeling::Camera* camera)
{
	size_t bytes = 0;
	return bytes;
}

size_t Modeling::DiskStream::write(Modeling::Light* light)
{
	size_t bytes = 0;
	return bytes;
}

size_t Modeling::DiskStream::write(Modeling::Mesh* mesh)
{
	unsigned int vertexQty = mesh->getVertQty();
	this->write(vertexQty);
	for (unsigned int i=0 ; i<vertexQty ; i++) {
		this->write(*mesh->getVertex(i));
	}

	unsigned int mapCoordsQty = mesh->getMapCoordsQty();
	this->write(mapCoordsQty);
	for (unsigned int i=0 ; i<mapCoordsQty ; i++) {
		this->write(*mesh->getMapCoord(i));
	}

	std::map<Vector3f*, unsigned int> vertexMap;
	for (unsigned int i=0 ; i<mesh->getVertQty() ; i++) {
		vertexMap[mesh->getVertex(i)] = i;
	}

	unsigned int polysQty = mesh->getPolygonsQty();
	this->write(polysQty);
	for (unsigned int i=0 ; i<polysQty ; i++) {
		Modeling::Polygon* polygon = mesh->getPolygon(i);
		Vector3ui index;
		for (unsigned int j=0 ; j<3 ;j++) {
			index[j] = vertexMap[ polygon->getVertex(j) ];
		}
		this->write(index);

		for (unsigned int j=0 ; j<3 ; j++) {
			this->write(*polygon->getNormal(j));
		}

		this->write(polygon->getMaterial()->getName());
	}

	vertexMap.clear();

	return 0;
}

size_t Modeling::DiskStream::writeMaterials(Modeling::Root* root)
{
	size_t bytes = 0;
	unsigned int matQty = root->getMaterialsQty();
	bytes += this->write(matQty);
	
	std::map< std::string, Modeling::Material* >* matMap = root->getMaterialMap();
	std::map< std::string, Modeling::Material* >::iterator it;
	for (it=matMap->begin() ; it != matMap->end() ; it++) {
		Modeling::Material* material = it->second;
		std::string name = material->getName();
		bytes += this->write(name);
		bytes += this->write(material);
	}
	return bytes;
}

size_t Modeling::DiskStream::write(Modeling::Material* material)
{
	size_t bytes = 0;

	Modeling::RGBf ambient = *material->getAmbient();
	bytes += this->write(ambient);

	Modeling::RGBf diffuse = *material->getDiffuse();
	bytes += this->write(diffuse);

	Modeling::RGBf specular = *material->getSpecular();
	bytes += this->write(specular);

	float shininess = material->getShininess();
	bytes += this->write(shininess);

	Modeling::Map* diffuseMap = material->getDiffuseMap();
	bytes += this->write(diffuseMap);

	return bytes;
}

size_t Modeling::DiskStream::write(Modeling::Map* map)
{
	size_t bytes = 0;

	std::string filename = map->getFileName();
	bytes += this->write(filename);

	return bytes;
}
